<?PHP
/*
 * $Id: barge-ftp.classes.php 129 2009-06-25 03:39:15Z namba@craid.co.jp $
 * =============================================================================
 * Barge - Web application framework on PHP5
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * =============================================================================
 * For more information, see http://barge.googlecode.com/
 */
namespace barge\ftp;

use barge\UnsupportedExtensionException;
use barge\io\IOException;
use barge\utils\ArrayList;

/**
 * @version 1.0 2009/05/30
 * @since barge 1.3 2009/05/30
 * @author Kenji Namba
 * @see IOException
 */
class FtpClientException extends IOException {}

/**
 * 
 * @version 1.0 2009/05/30
 * @since barge 1.3 2009/05/30
 * @author Kenji Namba
 */
class FtpClient
{
	const ASCII = FTP_ASCII;
	const BINARY = FTP_BINARY;
	
	/**
	 * @var resource
	 */
	private $ftp;

	/**
	 * @var デフォルトの転送モード（アスキー／バイナリ）
	 */
	private $defaultMode = self::BINARY;
	
	/**
	 * @param resource $ftp
	 */
	private function __construct($ftp) 
	{
		$this->ftp = $ftp;
	}

	/**
	 * @param string $host
	 * @param int $port
	 * @param string $username
	 * @param string $password
	 * @param bool $secure
	 * @return FtpClient
	 * @throws FtpClientException 接続に失敗した場合にスローされます
	 */
	public static function connect($host, $port = 21, $username = null, $password = null, $secure = false) 
	{
		if ($secure) {
			if (!function_exists('ftp_ssl_connect')) {
				$message = "function 'ftp_ssl_connect' dose not exists. OpenSSL unsupported.";
				throw new UnsupportedExtensionException($message);
			}
			$ftp = ftp_ssl_connect($host, $port, $timeout);
		} else {
			$ftp = ftp_connect($host, $port, $timeout);
		}
		if ($ftp === false) {
			$message = "Connection refused.";
			throw new FtpClientException($message);
		}
		if (!@ftp_login($ftp, $username, $password)) {
			ftp_close($ftp);
			throw new FtpClientException('Authentification faild.');
		}
		return new self($ftp);
	}

	/**
	 * タイムアウト時間を設定します
	 * @param float $msecond ミリ秒を設定します、1000ミリ秒未満が指定された場合は1000ミリ秒
	 * 			が設定されます
	 */
	public function setTimeout($msecond) 
	{
		ftp_set_option($this->ftp, FTP_TIMEOUT_SEC, $msecond < 1000 ? 1 : ($msecond / 1000));
	}
	
	/**
	 * パッシブモードを設定します
	 * @param bool $passive
	 */
	public function setPassiveMode($passive) 
	{
		ftp_pasv($this->ftp, $passive);
	}

	/**
	 * 現在のカレントディレクトリを移動する
	 */
	public function moveDir($dir) 
	{
		if (@ftp_chdir($this->ftp, $dir) === false) {
			$message = String::format('Fail to move directory : {0}', @$php_errormsg);
			throw new FtpClientException($message);	
		}
	}
	
	/**
	 * 現在のカレントディレクトリを移動する
	 */
	public function moveParentDir() 
	{
		if (@ftp_cdup($this->ftp) === false) {
			$message = String::format('Fail to move directory : {0}', @$php_errormsg);
			throw new FtpClientException($message);	
		}
	}

	/**
	 * 指定ディレクトリを削除する
	 * 
	 * @param string $dirpath ディレクトリパス
	 * @parma bool $recursive ディレクトリに含まれるすべてのファイルおよびディレクトリ
	 * 			を再帰的に削除するかどうか
	 */
	public function deleteDir($dirpath, $recursive = true) 
	{
		if ($recursive) {
			$files = $this->listDetails($dirpath, true, $recursive);
			foreach ($files as $file) {	/* @var $file FileDescriptor */
				if ($file->isDir()) {
					$this->deleteDir($dirpath.'/'.$file->getName());
				} else {
					ftp_delete($this->ftp, $dirpath.'/'.$file->getName());
				}
			}
			ftp_rmdir($this->ftp, $dirpath);
		} else {
			ftp_rmdir($this->ftp, $dirpath);
		}
	}
	
	/**
	 * 指定ファイルを削除する
	 * 
	 * @param string $filepath
	 * @throws FtpClientException
	 */
	public function deleteFile($filepath) 
	{
		if (@ftp_delete($this->ftp, $file) === false) {
			$message = String::format('Fail to remove a file size : {0}', @$php_errormsg);
			throw new FtpClientException($message);	
		}
		
	}
	
	/**
	 * 指定ファイルにパーミッションを設定する
	 * <pre>
	 * $ftpClient->setPermission('/tmp/sample.txt', 0644);
	 * </pre>
	 * @param string $filepath
	 * @param int $mode
	 * @throws FtpClientException
	 */
	public function setPermission($filepath, $mode) 
	{
		ftp_chmod($this->ftp, $mode, $filepath);
	}
	
	/**
	 * 指定されたファイルのサイズを取得する
	 * 
	 * @param string $file
	 * @throws FtpClientException
	 */
	public function getFileSize($file) 
	{
		if (@ftp_size($this->ftp, $file) === false) {
			$message = String::format('Fail to get a file size : {0}', @$php_errormsg);
			throw new FtpClientException($message);			
		}
	}

	/**
	 * ディレクトリを作成する
	 * 
	 * @param string $dir ディレクトリ
	 * @throws FtpClientException
	 */
	public function makeDir($dir) 
	{
		if (@ftp_mkdir($this->ftp, $dir) === false) {
			$message = String::format('Fail to make a directory : {0}', @$php_errormsg);
			throw new FtpClientException($message);
		}
	}
	
	/**
	 * @param string $dir ホストのディレクトリ名
	 * @param bool $all 隠しファイルを表示するか（-la）
	 * @throws FtpClientException
	 */
	public function listFiles($dirpath = '.', $all = false) 
	{
		$ret = @ftp_nlist($this->ftp, $all ? "ls '{$dirpath}'" : $dirpath);
		if ($ret === false) {
			$message = String::format('Fail to list directory : {0}', @$php_errormsg);
			throw new FtpClientException($message);
		}
		return new ArrayList($ret);
	}
	
	/**
	 * ローカルファイルをアップロードします
	 * @param string $localfile ローカルファイル
	 * @param string $hostfile アップロード先 
	 * @param int $mode アップロードモード、省略された場合はデフォルトのモードが使用されます
	 * @throws FtpClientException
	 */
	public function putFile($localfile, $hostfile, $mode = null) 
	{
		$mode = ($mode === null ? $this->defaultMode : $mode);
		if (@ftp_put($this->ftp, $hostfile, $localfile, $mode) === false) {
			$message = String::format('Fail to put a file : {0}', @$php_errormsg);
			throw new FtpClientException($message);
		}
	}

	/**
	 * ローカルファイルをアップロードします
	 * @param string $hostfile ダウンロードするファイル
	 * @param string $localfile ローカルのダウンロード場所
	 * @param int $mode アップロードモード、省略された場合はデフォルトのモードが使用されます
	 * @throws FtpClientException
	 */
	public function getFile($hostfile, $localfile, $mode = null) 
	{
		$mode = ($mode === null ? $this->defaultMode : $mode);
		if (@ftp_get($this->ftp, $localfile, $hostfile, $mode) === false) {
			$message = String::format('Fail to download a file : {0}', @$php_errormsg);
			throw new FtpClientException($message);
		}
	}
	
	/**
	 * @param string $dirpath ホストのディレクトリ名
	 * @param bool $all 隠しファイルを表示するか（-la）
	 * @param bool $recursive 再帰的にディレクトリを参照するか
	 * @return ArrayList
	 */
	public function listDetails($dirpath = '.', $all = false, $recursive = false) 
	{
//		http://www.php.net/ftp_rawlist
//		ftp_rawlist()
//		preg_match('/(.*?)\s+(\d+)\s+/')
//		array ftp_rawlist  ( resource $ftp_stream  , string $directory  [, bool $recursive= false  ] )
//string(65) "drwxr-x---   3 vincent  vincent      4096 Jul 12 12:16 public_ftp"

		$files = new ArrayList();
  		$rawlist = ftp_rawlist($this->ftp, $all ? "ls '{$dirpath}'" : $dirpath, $recursive);
		foreach ($rawlist as $v) {
			$info = array();
    		$vinfo = preg_split("/[\s]+/", $v, 9);
    		if ($vinfo[0] !== "total") {
    			$file = new FileDescriptor($vinfo[8], $vinfo[0][0] === 'd' ? 
    					FileDescriptor::DIR : FileDescriptor::FILE, 
    				$this->toDecimalMode($vinfo[0]),
    				$vinfo[2],
    				$vinfo[3],
    				$vinfo[0][0] === 'd' ? null : $this->toByte($vinfo[4]),
    				date_create("{$vinfo[5]} {$vinfo[6]} {$vinfo[7]}"));
    			$files->add($file);
//      			$info['chmod'] = $vinfo[0];
//      			$info['num'] = $vinfo[1];
//      			$info['owner'] = $vinfo[2];
//      			$info['group'] = $vinfo[3];
//				$info['size'] = $vinfo[4];
//				$info['month'] = $vinfo[5];
//				$info['day'] = $vinfo[6];
//				$info['time'] = $vinfo[7];
//				$info['name'] = $vinfo[8];
//				$rawlist[$info['name']] = $info;
			}
  		}
		return $files;
	}
	
	/**
	 * rwx-記述のモードを10進数に変換する
	 *
	 * @param string $mode
	 * @return int
	 */
	private function toDecimalMode($mode) 
	{
		$trans = array('-' => '0', 'r' => '4', 'w' => '2', 'x' => '1');
		$mode = substr(strtr($mode, $trans), 1);
		$array = str_split($chmod, 3);
		return array_sum(str_split($array[0])) . array_sum(str_split($array[1])) . array_sum(str_split($array[2]));
	}

	/**
	 * @param string $bytes
	 * @return int
	 */
	private function toByte($bytes) 
	{
	    $symbol = array('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB');
	    $exp = floor( log($bytes) / log(1024) );
	    return sprintf( '%.2f ' . $symbol[ $exp ], ($bytes / pow(1024, floor($exp))) );
	}
	
	/**
	 * 指定されたパスがディレクトリかどうかを返します
	 *
	 * @param string $dir
	 * @return bool
	 */
    public function isDir($dir)
    {
        if(@ftp_chdir($this->ftp, $dir )) {
            ftp_chdir($this->ftp, '/../');
            return true;
        } else {
            return false;
        }
    }
    
	/**
	 * 接続を終了します
	 */
	public function close() 
	{
		ftp_close($this->ftp);
		$this->ftp = null;
	}
	

}

/**
 * ファイルの詳細
 * @version 1.0 2009/05/30
 * @since barge 1.3 2009/05/30
 * @author Kenji Namba
 */
class FileDescriptor
{
	const DIR = 1;
	const FILE = 2;
	
	/**
	 * ファイル名
	 * @var string
	 */
	private $filename;
	
	/**
	 * タイプ
	 * @var int
	 */
	private $type;
	
	/**
	 * パーミッション
	 */
	private $permision;
	private $owner;
	private $group;
	private $size;
	
	/**
	 * @var DateTime 
	 */
	private $lastModified;

	/**
	 * コンストラクタ
	 *
	 * @param string $filename
	 * @param int $type
	 * @param int $permision
	 * @param string $owner
	 * @param string $group
	 * @param int $size
	 * @param DateTime $datetime
	 */
	public function __construct($filename, $type, $permision, $owner, $group, $size, DateTime $lastModified)
	{
		$this->filename = $filename;
		$this->type = $type;
		$this->permision = $permision;
		$this->owner = $owner;
		$this->group = $group;
		$this->size = $size;
		$this->lastModified = $lastModified;
	}
	
	/**
	 * @return string
	 */
	public function getName()
	{
		return $this->filename;
	}
	
	/**
	 * @return int
	 */
	public function getType()
	{
		return $this->type;
	}
	
	/**
	 * @return int
	 */
	public function getPermision()
	{
		return $this->permision;
	}
	
	/**
	 * @return string
	 */
	public function getOwner()
	{
		return $this->owner;
	}
	
	/**
	 * @return string
	 */
	public function getGroup()
	{
		return $this->group;
	}
	
	/**
	 * @return int
	 */
	public function getFileSize()
	{
		return $this->size;
	}
	
	/**
	 * @return DateTime
	 */
	public function getLastModified()
	{
		return $this->lastModified;
	}
	
	/**
	 * @return bool
	 */
	public function isDir()
	{
		return $this->type === self::DIR;
	}
	
	/**
	 * @return bool
	 */
	public function isFile()
	{
		return $this->type === self::FILE;
	}
}

